package com.spillman.signtab.fragments;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

import net.sf.andpdf.nio.ByteBuffer;
import net.sf.andpdf.pdfviewer.gui.FullScrollView;
import net.sf.andpdf.refs.HardReference;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.spillman.signtab.R;
import com.spillman.signtab.utils.FragmentCallbacks;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFImage;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFPaint;
import com.sun.pdfview.decrypt.PDFAuthenticationFailureException;
import com.sun.pdfview.decrypt.PDFPassword;
import com.sun.pdfview.font.PDFFont;

public class PdfViewerFragment extends Fragment {

	private static final int STARTPAGE = 1;
	private static final float STARTZOOM = 1.1f;

	private static final float MIN_ZOOM = 0.7f;
	private static final float MAX_ZOOM = 1.4f;
	private static final float ZOOM_INCREMENT = 1.4f;

	private static final String TAG = "PDFVIEWER";

	public static final String EXTRA_PDFFILENAME = "net.sf.andpdf.extra.PDFFILENAME";
	public static final String EXTRA_SHOWIMAGES = "net.sf.andpdf.extra.SHOWIMAGES";
	public static final String EXTRA_ANTIALIAS = "net.sf.andpdf.extra.ANTIALIAS";
	public static final String EXTRA_USEFONTSUBSTITUTION = "net.sf.andpdf.extra.USEFONTSUBSTITUTION";
	public static final String EXTRA_KEEPCACHES = "net.sf.andpdf.extra.KEEPCACHES";
	private static final String SAVE_DIR = Environment.getExternalStorageDirectory() + "/dcim/SignTab/";

	public static final boolean DEFAULTSHOWIMAGES = true;
	public static final boolean DEFAULTANTIALIAS = true;
	public static final boolean DEFAULTUSEFONTSUBSTITUTION = false;
	public static final boolean DEFAULTKEEPCACHES = false;

	private GraphView mOldGraphView;
	private GraphView mGraphView;
	private String pdffilename;
	private PDFFile mPdfFile;
	private int mPage;
	private Button mBtPage;
	private Button mBtPage2;
	private ImageButton bZoomOut;
	private ImageButton bZoomIn;
	private float mZoom;
	private File mTmpFile;
	private ProgressDialog progress;

	private PDFPage mPdfPage;

	private Thread backgroundThread;
	private Handler uiHandler;

	public static PdfViewerFragment newInstance() {
		return new PdfViewerFragment();
	}

	/**
	 * restore member variables from previously saved instance
	 * 
	 * @see onRetainNonConfigurationInstance
	 * @return true if instance to restore from was found
	 */
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		LinearLayout rootView = (LinearLayout) inflater.inflate(R.layout.document_to_sign, container, false);
		uiHandler = new Handler();
		if (mOldGraphView != null) {
			mGraphView = new GraphView(getActivity());
			mGraphView.mBi = mOldGraphView.mBi;
			mOldGraphView = null;
			mGraphView.mImageView.setImageBitmap(mGraphView.mBi);
			mGraphView.updateTexts();
			rootView.addView(mGraphView);
		} else {
			mGraphView = new GraphView(getActivity());

			PDFImage.sShowImages = DEFAULTSHOWIMAGES;
			PDFPaint.s_doAntiAlias = DEFAULTANTIALIAS;
			PDFFont.sUseFontSubstitution = DEFAULTUSEFONTSUBSTITUTION;
			HardReference.sKeepCaches = DEFAULTKEEPCACHES;

			if (getArguments() != null) {
				pdffilename = getArguments().getString(PdfViewerFragment.EXTRA_PDFFILENAME);
			}

			if (pdffilename == null)
				pdffilename = "no file selected";

			mPage = STARTPAGE;
			mZoom = STARTZOOM;

			rootView.addView(setContent(null));
		}
		init();
		return rootView;
	}

	private void init() {
		File storageDir = null;
		if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
			storageDir = new File(SAVE_DIR);
			if (storageDir != null) {
				if (!storageDir.mkdirs()) {
					if (!storageDir.exists()) {
						Log.d("Sign", "failed to create directory");
					}
				}
			}
		} else {
			Log.v(getString(R.string.app_name), "External storage is not mounted READ/WRITE.");
		}
	}

	private View setContent(String password) {
		View view = null;
		try {
			parsePDF(pdffilename, password);
			view = setView();
		} catch (PDFAuthenticationFailureException e) {
			Toast.makeText(getActivity(), "Passworded PDF", Toast.LENGTH_SHORT).show();
		}
		return view;
	}
	
	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		startRenderThread(mPage, mZoom);
	}

	private View setView() {

		LinearLayout.LayoutParams lpWrap = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
		LinearLayout.LayoutParams lpWrap1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 0, 1);
		LinearLayout vl = new LinearLayout(getActivity());
		vl.setLayoutParams(lpWrap);
		vl.setOrientation(LinearLayout.VERTICAL);
		vl.setGravity(Gravity.CENTER);
		mGraphView.setLayoutParams(lpWrap1);
		vl.addView(mGraphView);
		addNavButtons(vl);
		// remember page button for updates
		mBtPage2 = mBtPage;

		return vl;
	}

	private synchronized void startRenderThread(final int page, final float zoom) {
		if (backgroundThread != null)
			return;

		mGraphView.showText("reading page " + page + ", zoom:" + zoom);
		backgroundThread = new Thread(new Runnable() {
			public void run() {
				try {
					if (mPdfFile != null) {
						showPage(page, zoom);
					}
				} catch (Exception e) {
					Log.e(TAG, e.getMessage(), e);
				}
				backgroundThread = null;
			}
		});
		updateImageStatus();
		backgroundThread.start();
	}

	private void updateImageStatus() {
		if (backgroundThread == null) {
			mGraphView.updateUi();
			return;
		}
		mGraphView.updateUi();
		mGraphView.postDelayed(new Runnable() {
			public void run() {
				updateImageStatus();
			}
		}, 1000);
	}

	private void zoomIn() {
		if (mPdfFile != null) {
			if (mZoom < MAX_ZOOM) {
				mZoom *= ZOOM_INCREMENT;
				if (mZoom > MAX_ZOOM)
					mZoom = MAX_ZOOM;

				if (mZoom >= MAX_ZOOM) {
					Log.d(TAG, "Disabling zoom in button");
					bZoomIn.setEnabled(false);
				} else
					bZoomIn.setEnabled(true);
				bZoomOut.setEnabled(true);
				startRenderThread(mPage, mZoom);
			}
		}
	}

	private void zoomOut() {
		if (mPdfFile != null) {
			if (mZoom > MIN_ZOOM) {
				mZoom /= ZOOM_INCREMENT;
				if (mZoom < MIN_ZOOM)
					mZoom = MIN_ZOOM;

				if (mZoom <= MIN_ZOOM) {
					Log.d(TAG, "Disabling zoom out button");
					bZoomOut.setEnabled(false);
				} else
					bZoomOut.setEnabled(true);
				bZoomIn.setEnabled(true);
				startRenderThread(mPage, mZoom);
			}
		}
	}

	private void nextPage() {
		if (mPdfFile != null) {
			if (mPage < mPdfFile.getNumPages()) {
				mPage += 1;
				bZoomOut.setEnabled(true);
				bZoomIn.setEnabled(true);
				progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page " + mPage, true, true);
				startRenderThread(mPage, mZoom);
			}
		}
	}

	private void prevPage() {
		if (mPdfFile != null) {
			if (mPage > 1) {
				mPage -= 1;
				bZoomOut.setEnabled(true);
				bZoomIn.setEnabled(true);
				progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page " + mPage, true, true);
				startRenderThread(mPage, mZoom);
			}
		}
	}

	private void addNavButtons(ViewGroup vg) {

		LinearLayout.LayoutParams lpChild1 = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1);
		LinearLayout.LayoutParams lpWrap10 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
				LinearLayout.LayoutParams.WRAP_CONTENT);

		LinearLayout hl = new LinearLayout(getActivity());
		hl.setLayoutParams(lpWrap10);
		hl.setOrientation(LinearLayout.HORIZONTAL);
		hl.setGravity(Gravity.CENTER);
		hl.setPadding(8, 8, 8, 8);
		hl.setBackgroundColor(getResources().getColor(android.R.color.transparent));

		// zoom out button
		bZoomOut = new ImageButton(getActivity());
		bZoomOut.setBackground(null);
		bZoomOut.setLayoutParams(lpChild1);
		bZoomOut.setPadding(8, 8, 8, 8);
		bZoomOut.setImageResource(getZoomOutImageResource());
		bZoomOut.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				zoomOut();
			}
		});
		hl.addView(bZoomOut);

		// zoom in button
		bZoomIn = new ImageButton(getActivity());
		bZoomIn.setBackground(null);
		bZoomIn.setLayoutParams(lpChild1);
		bZoomIn.setPadding(8, 8, 8, 8);
		bZoomIn.setImageResource(getZoomInImageResource());
		bZoomIn.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				zoomIn();
			}
		});
		hl.addView(bZoomIn);

		// page button
		mBtPage = new Button(getActivity());
		mBtPage.setLayoutParams(lpChild1);
		mBtPage.setPadding(8, 8, 8, 8);
		String maxPage = ((mPdfFile == null) ? "0" : Integer.toString(mPdfFile.getNumPages()));
		mBtPage.setText(mPage + "/" + maxPage);
		mBtPage.setBackgroundColor(getResources().getColor(R.color.button_blue));
		hl.addView(mBtPage);

		// prev button
		ImageButton bPrev = new ImageButton(getActivity());
		bPrev.setBackground(null);
		bPrev.setLayoutParams(lpChild1);
		bPrev.setPadding(8, 8, 8, 8);
		bPrev.setImageResource(getPreviousPageImageResource());
		bPrev.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				prevPage();
			}
		});
		hl.addView(bPrev);

		// next button
		ImageButton bNext = new ImageButton(getActivity());
		bNext.setBackground(null);
		bNext.setLayoutParams(lpChild1);
		bNext.setPadding(8, 8, 8, 8);
		bNext.setImageResource(getNextPageImageResource());
		bNext.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				nextPage();
			}
		});
		hl.addView(bNext);

		vg.addView(hl);
	}

	private class GraphView extends FullScrollView {
		private Bitmap mBi;
		private ImageView mImageView;

		public GraphView(Context context) {
			super(context);

			// layout params
			LinearLayout.LayoutParams lpWrap1 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
					LayoutParams.WRAP_CONTENT);
			LinearLayout.LayoutParams lpWrap10 = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
					LayoutParams.WRAP_CONTENT);

			// vertical layout
			LinearLayout vl = new LinearLayout(context);
			vl.setLayoutParams(lpWrap10);
			vl.setOrientation(LinearLayout.VERTICAL);
			vl.setGravity(Gravity.CENTER);

			if (mOldGraphView == null)
				progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page", true, true);

			mImageView = new ImageView(context);
			setPageBitmap(null);
			updateImage();
			mImageView.setLayoutParams(lpWrap1);
			mImageView.setPadding(8, 8, 8, 8);
			mImageView.setOnClickListener(new View.OnClickListener() {

				@Override
				public void onClick(View v) {
					((FragmentCallbacks) getActivity()).getSign();
				}
			});
			vl.addView(mImageView);

			setBackgroundColor(getResources().getColor(android.R.color.transparent));
			setHorizontalScrollBarEnabled(true);
			setHorizontalFadingEdgeEnabled(true);
			setVerticalScrollBarEnabled(true);
			setVerticalFadingEdgeEnabled(true);
			addView(vl);
		}

		private void showText(String text) {
			Log.i(TAG, "ST='" + text + "'");
			updateUi();
		}

		private void updateUi() {
			uiHandler.post(new Runnable() {
				public void run() {
					updateTexts();
				}
			});
		}

		private void updateImage() {
			uiHandler.post(new Runnable() {
				public void run() {
					mImageView.setImageBitmap(mBi);

				}
			});
		}

		private void setPageBitmap(Bitmap bi) {
			if (bi != null) {
				mBi = bi;
			}
		}

		protected void updateTexts() {
			if (mPdfPage != null) {
				if (mBtPage != null)
					mBtPage.setText(mPdfPage.getPageNumber() + "/" + mPdfFile.getNumPages());
				if (mBtPage2 != null)
					mBtPage2.setText(mPdfPage.getPageNumber() + "/" + mPdfFile.getNumPages());
			}
		}
	}

	private void showPage(int page, float zoom) throws Exception {
		try {
			// free memory from previous page
			mGraphView.setPageBitmap(null);
			mGraphView.updateImage();

			// Only load the page if it's a different page (i.e. not just
			// changing the zoom level)
			if (mPdfPage == null || mPdfPage.getPageNumber() != page) {
				mPdfPage = mPdfFile.getPage(page, true);
			}
			float width = mPdfPage.getWidth();
			float height = mPdfPage.getHeight();
			RectF clip = null;
			Bitmap bi = mPdfPage.getImage((int) (width * zoom), (int) (height * zoom), clip, true, true);
			mGraphView.setPageBitmap(bi);
			mGraphView.updateImage();

			if (progress != null)
				progress.dismiss();
		} catch (Throwable e) {
			Log.e(TAG, e.getMessage(), e);
			mGraphView.showText("Exception: " + e.getMessage());
		}
	}

	private void parsePDF(String filename, String password) throws PDFAuthenticationFailureException {
		try {
			File f = new File(filename);
			long len = f.length();
			if (len == 0) {
				mGraphView.showText("file '" + filename + "' not found");
			} else {
				mGraphView.showText("file '" + filename + "' has " + len + " bytes");
				openFile(f, password);
			}
		} catch (PDFAuthenticationFailureException e) {
			throw e;
		} catch (Throwable e) {
			e.printStackTrace();
			mGraphView.showText("Exception: " + e.getMessage());
		}
	}

	/**
	 * <p>
	 * Open a specific pdf file. Creates a DocumentInfo from the file, and opens
	 * that.
	 * </p>
	 * 
	 * <p>
	 * <b>Note:</b> Mapping the file locks the file until the PDFFile is closed.
	 * </p>
	 * 
	 * @param file
	 *            the file to open
	 * @throws IOException
	 */
	public void openFile(File file, String password) throws IOException {
		// first open the file for random access
		RandomAccessFile raf = new RandomAccessFile(file, "r");

		// extract a file channel
		FileChannel channel = raf.getChannel();

		// now memory-map a byte-buffer
		ByteBuffer bb = ByteBuffer.NEW(channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
		// create a PDFFile from the data
		if (password == null)
			mPdfFile = new PDFFile(bb);
		else
			mPdfFile = new PDFFile(bb, new PDFPassword(password));

		mGraphView.showText("Anzahl Seiten:" + mPdfFile.getNumPages());
		raf.close();
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		if (mTmpFile != null) {
			mTmpFile.delete();
			mTmpFile = null;
		}
	}

	public int getPreviousPageImageResource() {
		return R.drawable.left_arrow;
	}; // R.drawable.left_arrow

	public int getNextPageImageResource() {
		return R.drawable.right_arrow;
	}; // R.drawable.right_arrow

	public int getZoomInImageResource() {
		return R.drawable.zoom_in;
	}; // R.drawable.zoom_in

	public int getZoomOutImageResource() {
		return R.drawable.zoom_out;
	}; // R.drawable.zoom_out

}